#include "Args.h"
#include <algorithm>
#include "effects/Gray.h"
#include "effects/Smooth.h"
#include "effects/PyrDown.h"
#include "effects/PyrUp.h"
#include "effects/Canny.h"
#include "effects/Sobel.h"
#include "utils/StringUtils.h"
#include "utils/Logger.h"
#include "effects/MorphologyEx.h"
#include "effects/Threshold.h"
#include "effects/AdaptiveThreshold.h"
#include "effects/Laplase.h"
#include "effects/Contours.h"
#include "effects/ZeroColor.h"
#include "effects/HoughLines.h"
#include "effects/Equalize.h"
#include "effects/BackProjection.h"
#include "effects/ConnectedComponents.h"
#include "effects/GoodFeaturesToTrack.h"
#include "effects/SaveImage.h"

string Args::usage() {
    return  "Usage: hello [options] <source>\n"
            "\n"
            "   Sources:\n"
            "\n"
            "      -i   --image=<file>          Show image from file.\n"
            "\n"
            "      -v   --video=<file>          Show video from file.\n"
            "\n"
            "      -c   --camera[=<n>]          Show video from camera.\n"
            "                                   <n> is the camera number.\n"
            "\n"
            "   Options:\n"
            "\n"
            "      -h   --help                  Show this text.\n"
            "\n"
            "      -d   --debug                 Prints debug info.\n"
            "\n"
            "           --save-image=<filename> Saves current image to filename.\n"
            "\n"
            "      -n   --no-original           Doesn't show the original window.\n"
            "\n"
            "      -o   --output                The name of output window. Default: Output.\n"
            "\n"
            "      -z   --zero=<color>          Zeros a color.\n"
            "                                   <color> is color index.\n"
            "\n"
            "      -e   --equalize              Equalize image histogram.\n"
            "\n"
            "      -g   --gray                  Converts an image to gray scale.\n"
            "           --grey\n"
            "\n"
            "      -s   --smooth[=<n>[:<a>]]    Smoothes input.\n"
            "                                   <n> is smooth type:\n"
            "                                       0 is BLUR_NO_SCALE\n"
            "                                       1 is BLUR\n"
            "                                       2 is GAUSSIAN (default)\n"
            "                                       3 is MEDIAN\n"
            "                                   <a> is aperture. Default: 3.\n"
            "\n"
            "           --pyrdown               Smoothes the input with gaussian\n"
            "                                   kernel and then down-samples it.\n"
            "\n"
            "           --pyrup                 Up-samples image and smoothes the\n"
            "                                   result with gaussian kernel.\n"
            "\n"
            "           --sobel[=<x>:<y>[:<a>]] Sobel derivative.\n"
            "                                   <x> is x order. Default: 1.\n"
            "                                   <y> is y order. Default: 1.\n"
            "                                   <a> is aperture. Default: 3.\n"
            "\n"
            "           --laplase[=<a>]         Laplase.\n"
            "                                   <a> is aperture. Default: 3.\n"
            "\n"
            "           --canny[=<l>:<h>[:<a>]] Detect edges.\n"
            "                                   <l> is low threshold. Default: 10.\n"
            "                                   <h> is high threshold. Default: 100.\n"
            "                                   <a> is aperture. Default: 3.\n"
            "\n"
            "           --erode[=<i>[:<a>]]     Erode = Min operator.\n"
            "                                   <i> are iterations. Default: 1.\n"
            "                                   <a> is aperture. Default: 3.\n"
            "\n"
            "           --dilate[=<i>[:<a>]]    Dilate = Max operator.\n"
            "                                   <i> are iterations. Default: 1.\n"
            "                                   <a> is aperture. Default: 3.\n"
            "\n"
            "           --open[=<i>[:<a>]]      Open = Erode then dilate.\n"
            "                                   <i> are iterations. Default: 1.\n"
            "                                   <a> is aperture. Default: 3.\n"
            "\n"
            "           --close[=<i>[:<a>]]     Close = Dilate then erode.\n"
            "                                   <i> are iterations. Default: 1.\n"
            "                                   <a> is aperture. Default: 3.\n"
            "\n"
            "           --gradient[=<i>[:<a>]]  Gradient = Dilate - Erode.\n"
            "                                   <i> are iterations. Default: 1.\n"
            "                                   <a> is aperture. Default: 3.\n"
            "\n"
            "           --tophat[=<i>[:<a>]]    Top hat = Image - Open.\n"
            "                                   <i> are iterations. Default: 1.\n"
            "                                   <a> is aperture. Default: 3.\n"
            "\n"
            "           --blackhat[=<i>[:<a>]]  Black hat = Close - Image.\n"
            "                                   <i> are iterations. Default: 1.\n"
            "                                   <a> is aperture. Default: 3.\n"
            "\n"
            "      -t   --threshold=<v>:<m>[:<t>]\n"
            "                                   Threshold.\n"
            "                                   <v> is threshold value.\n"
            "                                   <m> is max value.\n"
            "                                   <t> is threshold type:\n"
            "                                           0 is BINARY. (default)\n"
            "                                           1 is BINARY_INV.\n"
            "                                           2 is TRUNC.\n"
            "                                           3 is TOZERO.\n"
            "                                           4 is TOZERO_INV.\n"
            "\n"
            "      -a   --adaptive-threshold=<v>[:<m>[:<t>[:<a>[:<c>]]]]\n"
            "                                   Adaptive threshold.\n"
            "                                   <v> is max value.\n"
            "                                   <m> is adaptive method:\n"
            "                                           0 is MEAN. (default)\n"
            "                                           1 is GAUSSIAN.\n"
            "                                   <t> is threshold type:\n"
            "                                           0 is BINARY. (default)\n"
            "                                           1 is BINARY_INV.\n"
            "                                           2 is TRUNC.\n"
            "                                           3 is TOZERO.\n"
            "                                           4 is TOZERO_INV.\n"
            "                                   <a> is aperture. Default: 3.\n"
            "                                   <c> is a constant subtracted from mean.\n"
            "                                           Default: 5.\n"
            "\n"
            "           --contours              Finds and draws contours.\n"
            "                                   Red for contours.\n"
            "                                   Green for holes.\n"
            "\n"
            "           --houghlines=<t>:<l>:<s>\n"
            "                                   Draws houghlines in blue.\n"
            "                                   <t> is threshold value.\n"
            "                                   <l> is minimum line length.\n"
            "                                   <s> is separation between\n"
            "                                           collinear segments.\n"
            "\n"
            "           --backproj              Back projection.\n"
            "\n"
            "           --cc                    Connected components.\n"
            "\n"
            "           --features              Good features to track.\n"
            "\n";
}

const char* Args::optstring = "hdi:v:cno:z:egst:a:";
const option Args::options[] = {
    {"help", no_argument, NULL, 'h'},
    {"debug", no_argument, NULL, 'd'},
    {"image", required_argument, NULL, 'i'},
    {"video", required_argument, NULL, 'v'},
    {"camera", optional_argument, NULL, 'c'},
    {"save-image", required_argument, &longOption, SAVEIMAGE},
    {"no-original", no_argument, NULL, 'n'},
    {"output", required_argument, NULL, 'o'},
    {"zero", required_argument, NULL, 'z'},
    {"equalize", no_argument, NULL, 'e'},
    {"gray", no_argument, NULL, 'g'},
    {"grey", no_argument, NULL, 'g'},
    {"smooth", optional_argument, NULL, 's'},
    {"pyrdown", no_argument, &longOption, PYRDOWN},
    {"pyrup", no_argument, &longOption, PYRUP},
    {"sobel", optional_argument, &longOption, SOBEL},
    {"laplase", optional_argument, &longOption, LAPLASE},
    {"canny", optional_argument, &longOption, CANNY},
    {"erode", optional_argument, &longOption, ERODE},
    {"dilate", optional_argument, &longOption, DILATE},
    {"open", optional_argument, &longOption, OPEN},
    {"close", optional_argument, &longOption, CLOSE},
    {"gradient", optional_argument, &longOption, GRADIENT},
    {"tophat", optional_argument, &longOption, TOPHAT},
    {"blackhat", optional_argument, &longOption, BLACKHAT},
    {"threshold", required_argument, NULL, 't'},
    {"adaptive-threshold", required_argument, NULL, 'a'},
    {"contours", no_argument, &longOption, CONTOURS},
    {"houghlines", required_argument, &longOption, HOUGHLINES},
    {"backproj", no_argument, &longOption, BACKPROJECTION},
    {"cc", no_argument, &longOption, CONNECTEDCOMPONENTS},
    {"features", optional_argument, &longOption, GOODFEATURES},
    {NULL, 0, NULL, 0}
};
int Args::longOption = -1;

Args::Args(const int argc, char** argv) :
        effects(new Effects()),
        argc(argc),
        argv(argv),
        help(false),
        noOriginal(false),
        output("Output"),
        image(NULL),
        video(NULL)
{
}

Args::~Args() {
    if (image) cvReleaseImage(&image);
    if (video) cvReleaseCapture(&video);

    delete effects;
}

void Args::parse() {
    int ch;
    int cameraIndex;
    vector<int> count, numbers;

    while ((ch = getopt_long(argc, argv, optstring, options, NULL)) != -1) {
        count.clear();
        
        switch (ch) {
            case 'h':
                Log << "Option --help" << endl;
                help = true;
                return;

            case 'd':
                Logger::debug = true;
                break;

            case 'i':
                Log << "Option --image: " << optarg << endl;
                image = cvLoadImage(optarg);
                if (NULL == image) {
                    cerr << "Invalid image file: " << optarg << endl;
                    exit(1);
                }
                break;

            case 'v':
                Log << "Option --video: " << optarg << endl;
                if (video) {
                    Log << "Releasing older capture." << endl;
                    cvReleaseCapture(&video);
                }

                video = cvCreateFileCapture(optarg);
                if (NULL == video) {
                    cerr << "Invalid video file: " << optarg << endl;
                    exit(1);
                }
                break;

            case 'c':
                Log << "Option --camera" << endl;
                if (video) {
                    Log << "Releasing older capture." << endl;
                    cvReleaseCapture(&video);
                }

                cameraIndex = 0;
                if (optarg) {
                    count.push_back(1);
                    numbers = parseIntegersArgument("camera", count);
                    cameraIndex = numbers[0];
                }

                video = cvCreateCameraCapture(cameraIndex);
                if (NULL == video) {
                    cerr << "No camera found." << endl;
                    exit(1);
                }
                break;

            case 'n':
                Log << "Option --no-original" << endl;
                noOriginal = true;
                break;

            case 'o':
                Log << "Option --output" << endl;
                output = optarg;
                break;

            case 'z':
                Log << "Option --zero" << endl;
                count.push_back(1);
                numbers = parseIntegersArgument("zero", count);

                effects->push(new ZeroColor(numbers[0]));
                break;

            case 'e':
                Log << "Option --equalize" << endl;
                effects->push(new Equalize());
                break;

            case 'g':
                Log << "Option --gray" << endl;
                effects->push(new Gray());
                break;

            case 's':
                Log << "Option --smooth" << endl;
                if (NULL == optarg)
                    effects->push(new Smooth());
                else {
                    count.push_back(1);
                    count.push_back(2);
                    vector<int> numbers = parseIntegersArgument("smooth", count);

                    if (numbers.size() == 1)
                        effects->push(new Smooth(numbers[0]));
                    else if (numbers.size() == 2)
                        effects->push(new Smooth(numbers[0], numbers[1]));
                }
                break;

            case 't':
                Log << "Option --threshold" << endl;
                count.push_back(2);
                count.push_back(3);
                numbers = parseIntegersArgument("threshold", count);

                if (numbers.size() == 2)
                    effects->push(new Threshold(numbers[0], numbers[1]));
                else
                    effects->push(new Threshold(numbers[0], numbers[1], numbers[2]));
                break;

            case 'a':
                Log << "Option --adaptive-threshold" << endl;
                count.push_back(1);
                count.push_back(2);
                count.push_back(3);
                count.push_back(4);
                count.push_back(5);
                numbers = parseIntegersArgument("adaptive-threshold", count);

                if (numbers.size() == 1)
                    effects->push(new AdaptiveThreshold(numbers[0]));
                else if (numbers.size() == 2)
                    effects->push(new AdaptiveThreshold(numbers[0], numbers[1]));
                else if (numbers.size() == 3)
                    effects->push(new AdaptiveThreshold(numbers[0], numbers[1], numbers[2]));
                else if (numbers.size() == 4)
                    effects->push(new AdaptiveThreshold(numbers[0], numbers[1], numbers[2], numbers[3]));
                else if (numbers.size() == 5)
                    effects->push(new AdaptiveThreshold(numbers[0], numbers[1], numbers[2], numbers[3], numbers[4]));
                break;

            case 0:
                parseLongOptions();
                break;

            case '?':
                exit(1);

            default:
                cerr << usage() << endl;
                exit(1);
        }
    }
}

void Args::parseLongOptions() {
    vector<int> count, numbers;
    Effects *tmp;
    
    switch (longOption) {
        case SAVEIMAGE:
            Log << "Option --save-image" << endl;
            effects->push(new SaveImage(optarg));
            break;

        case PYRDOWN:
            Log << "Option --pyrdown" << endl;
            effects->push(new PyrDown());
            break;

        case PYRUP:
            Log << "Option --pyrup" << endl;
            effects->push(new PyrUp());
            break;

        case SOBEL:
            Log << "Option --sobel" << endl;
            if (NULL == optarg)
                effects->push(new Sobel());
            else {
                count.push_back(2);
                count.push_back(3);
                numbers = parseIntegersArgument("sobel", count);

                if (numbers.size() == 2)
                    effects->push(new Sobel(numbers[0], numbers[1]));
                else
                    effects->push(new Sobel(numbers[0], numbers[1], numbers[2]));
            }
            break;

        case LAPLASE:
            Log << "Option --laplase" << endl;
            if (NULL == optarg)
                effects->push(new Laplase());
            else {
                count.push_back(1);
                numbers = parseIntegersArgument("laplase", count);

                effects->push(new Laplase(numbers[0]));
            }
            break;

        case CANNY:
            Log << "Option --canny" << endl;
            if (NULL == optarg)
                effects->push(new Canny());
            else {
                count.push_back(2);
                count.push_back(3);
                numbers = parseIntegersArgument("canny", count);

                if (numbers.size() == 2)
                    effects->push(new Canny(numbers[0], numbers[1]));
                else
                    effects->push(new Canny(numbers[0], numbers[1], numbers[2]));
            }
            break;

        case ERODE:
            parseMorphologyExOption("erode", CV_MOP_ERODE);
            break;

        case DILATE:
            parseMorphologyExOption("dilate", CV_MOP_DILATE);
            break;

        case OPEN:
            parseMorphologyExOption("open", CV_MOP_OPEN);
            break;

        case CLOSE:
            parseMorphologyExOption("close", CV_MOP_CLOSE);
            break;

        case GRADIENT:
            parseMorphologyExOption("gradient", CV_MOP_GRADIENT);
            break;

        case TOPHAT:
            parseMorphologyExOption("tophat", CV_MOP_TOPHAT);
            break;

        case BLACKHAT:
            parseMorphologyExOption("blackhat", CV_MOP_BLACKHAT);
            break;

        case CONTOURS:
            Log << "Option --contours" << endl;
            tmp = new Effects();
            tmp->push(new Contours(effects));
            effects = tmp;
            break;

        case HOUGHLINES:
            Log << "Option --houghlines" << endl;
            count.push_back(3);
            numbers = parseIntegersArgument("houghlines", count);

            tmp = new Effects();
            tmp->push(new HoughLines(numbers[0], numbers[1], numbers[2], effects));
            effects = tmp;
            break;

        case BACKPROJECTION:
            Log << "Option --backproj" << endl;
            effects->push(new BackProjection());
            break;

        case CONNECTEDCOMPONENTS:
            Log << "Option --cc" << endl;
            tmp = new Effects();
            tmp->push(new ConnectedComponents(effects));
            effects = tmp;
            break;

        case GOODFEATURES:
            Log << "Option --features" << endl;
            {
                GoodFeaturesToTrack* effect = new GoodFeaturesToTrack();

                if (optarg) {
                    Log << "Option --features argument: " << optarg << endl;

                    effect->mask = cvLoadImage(optarg, CV_LOAD_IMAGE_GRAYSCALE);

                    if (!effect->mask)
                        cerr << "Mask for --features failed to load: " << optarg << endl;
                }

                effects->push(effect);
            }
            break;

        default:
            cerr << "Unrecognized long option." << endl;
            exit(1);
    }  
}

void Args::parseMorphologyExOption(string option, int operation) {
    Log << "Option --" << option << endl;
    if (NULL == optarg)
        effects->push(new MorphologyEx(operation));
    else {
        vector<int> count;
        count.push_back(1);
        count.push_back(2);
        vector<int> numbers = parseIntegersArgument(option, count);

        if (numbers.size() == 1)
            effects->push(new MorphologyEx(operation, numbers[0]));
        else
            effects->push(new MorphologyEx(operation, numbers[0], numbers[1]));
    }
}

vector<int> Args::parseIntegersArgument(string option, const vector<int>& count) const {
    Log << "Option --" << option << " argument: " << optarg << endl;
    
    vector<string> tokens = StringUtils::split(optarg, ":");

    if (find(count.begin(), count.end(), tokens.size()) == count.end()) {
        cerr << "Invalid --" << option << " option argument: " <<
                "Wrong arguments count." << endl;
        exit(1);
    }

    vector<int> numbers;
    try {
        transform(tokens.begin(), tokens.end(),
                back_inserter(numbers),
                StringUtils::toInt);
    }
    catch (NumberFormatException ex) {
        cerr << "Invalid --" << option << " option argument: " <<
                "Arguments should be integers." << endl;
        exit(1);
    }

    return numbers;
}
